# -*- coding: utf-8 -*- 
"""
========================================================================================================================
@project : my-sanic
@file: myCache
@Author: mengying
@email: 652044581@qq.com
@date: 2023/3/14 9:40
@desc: 异步缓存组件--实例查看api-demo
========================================================================================================================
"""
import asyncio
import logging
import time
from threading import Lock
from typing import Union, Any, Dict

from aredis import StrictRedis, StrictRedisCluster
from redis import Redis, RedisCluster


class SingletonMeta(type):
    """元类——有限的单例模式
    当初始化参数包含new=True时，将构造一个新的对象
    """
    __instance = None
    __lock = Lock()

    def __call__(cls, *args, **kwargs):
        with cls.__lock:
            new = kwargs.pop('new', None)
            if new is True:
                return super().__call__(*args, **kwargs)
            if not cls.__instance:
                cls.__instance = super().__call__(*args, **kwargs)
        return cls.__instance


class MessageStation(metaclass=SingletonMeta):

    def __setattr__(self, key, value):
        """添加/修改属性会触发它的执行"""
        self.__dict__[key] = value

    def __getattr__(self, item):
        """只有在使用点调用属性且属性不存在的时候才会触发"""
        return None

    def __delattr__(self, item):
        """删除属性的时候会触发"""
        super.__delattr__(self, item)


class MemoryEngine:
    """
    本地内存作为后端缓存引擎，不支持分布式
    只支持get、set方法
    """

    def __init__(self):
        self.namespace = {}
        self._check_time = 0
        self._check_interval = 60

    def delete(self, key: str) -> None:
        """删除指定缓存"""
        if key in self.namespace:
            del self.namespace[key]

    def et_clear(self) -> None:
        """清理超时缓存"""
        clear_names = []
        if time.time() > self._check_time + self._check_interval:
            self._check_time = time.time()
            for name, block in self.namespace.items():
                if block.ttl < -1:
                    clear_names.append(name)
        for name in clear_names:
            del self.namespace[name]


class SyncMemoryEngine(MemoryEngine):

    def ttl(self, name) -> int:
        self.et_clear()
        if name not in self.namespace:
            return -1
        return int(self.namespace[name].ttl)

    def get(self, name):
        self.et_clear()
        if name not in self.namespace:
            return None
        return self.namespace[name].val

    def set(self, name, value, ex=None, px=None, nx=False, xx=False):
        if nx and name in self.namespace:
            return
        if xx and name not in self.namespace:
            return
        self.namespace[name] = DataBlock(name, value, ex, px)
        if len(value) > 16384 and (ex or px):
            # 实验性功能 大容量缓存清理机制 避免长时间不使用缓存下占用内存
            life = ex if ex else px // 1000
            loop = asyncio.get_event_loop()
            loop.call_later(life * 2, self.delete, name)


class AsyncMemoryEngine(MemoryEngine):

    async def ttl(self, name) -> int:
        self.et_clear()
        if name not in self.namespace:
            return -1
        return int(self.namespace[name].ttl)

    async def get(self, name):
        self.et_clear()
        if name not in self.namespace:
            return None
        return self.namespace[name].val

    async def set(self, name, value, ex=None, px=None, nx=False, xx=False):
        if nx and name in self.namespace:
            return
        if xx and name not in self.namespace:
            return
        self.namespace[name] = DataBlock(name, value, ex, px)
        if len(value) > 16384 and (ex or px):
            # 实验性功能 大容量缓存清理机制 避免长时间不使用缓存下占用内存
            life = ex if ex else px // 1000
            loop = asyncio.get_event_loop()
            loop.call_later(life * 2, self.delete, name)


class DataBlock:
    """内存数据块 封装了有效期"""

    def __init__(self, name: str, value: Any, ex: float = None, px: float = None):
        """
        :param name: key名
        :param value: 存储value
        :param ex: 生命周期，单位秒
        :param px: 生命周期，单位毫秒
        """
        self._name = name
        self._value = value
        self.et = time.time() - 1
        if ex:
            self.et += ex
        if px:
            self.et += (px / 1000)
        if not ex and not px:
            self._ttl = -1

    @property
    def val(self):
        return self._value if self.ttl >= -1 else None

    @property
    def ttl(self):
        if hasattr(self, '_ttl'):
            return self._ttl
        return self.et - time.time()

    def __repr__(self):
        return f'<name={self._name}>'


class Cache(metaclass=SingletonMeta):
    """一个基于redis封装的异步缓存类，它可以快速方便切换多个缓存库
    Cache类默认使用default缓存库，你可以使用select(db_name)切换其他库， 并且select支持
    链式调用，但select方法并不会改变原对象指向的default缓存库
    Cache对象通过反射拥有了StrictRedis和StrictRedisCluster类下的所有方法，你可以直接对
    对象执行redis命令，此外Cache还封装了一个方法execute(command, *args, **kwargs)
    相比于反射方法，使用execute方法会自动对返回数据解码
    针对字符串类型，Cache对get和set方法作了优化，当使用get和set方法时，可以同时传递一个序列化器，
    它会查询和存储时自动使用序列化器，也就是说你可以使用set方法存储任意序列化器支持的对象
    """

    def __init__(self, config: dict = None):
        """
        :param config: 缓存数据库字典
        :return: Cache对象
        """
        self._default = 'default'
        self._caches = {}
        self._config = {} if config is None else config
        self.mode = self._config.pop('mode', "sync")
        self.cacheValue: dict = {}

        # 默认增加本地cache
        self._default_cache_load()

        # 增加rediscache的功能
        self._redis_cache_load()

    def _redis_cache_load(self):
        """redis的配置"""

        # 没有配置redis的情况
        if not self._config.get('host', None):
            logging.warning("cache not get redis config, only can use default")
            self._caches["redis"] = None
            return

        # 配置redis或者redis集群
        if self.mode == "sync":
            if 'startup_nodes' in self._config:
                self._caches["redis"] = RedisCluster(**self._config, decode_responses=True)
            else:
                self._caches["redis"] = Redis(**self._config, decode_responses=True)
        else:
            if 'startup_nodes' in self.cacheValue:
                self._caches["redis"] = StrictRedisCluster(**self.cacheValue, decode_responses=True)
            else:
                self._caches["redis"] = StrictRedis(**self._config, decode_responses=True)

        # 测试redis的链接情况
        connectionSuccess = self._caches["redis"].ping()
        if connectionSuccess is True:
            logging.info("cache redis has connected successfully")
        elif connectionSuccess is False:
            logging.debug("cache redis has connected failed, pleasure checkout your config")

    def _default_cache_load(self):
        """本地的缓存配置"""
        if self.mode == "sync":
            self._caches["default"] = SyncMemoryEngine()
        else:
            self._caches["default"] = AsyncMemoryEngine()

    @property
    def all(self) -> Dict[str, Union[StrictRedis, StrictRedisCluster, RedisCluster, Redis]]:
        """返回全部缓存数据库"""
        return self._caches

    @property
    def current_db(self) -> Union[StrictRedis, StrictRedisCluster, RedisCluster, Redis]:
        """返回缓存对象指向的缓存数据库"""
        return self._caches[self._default]

    def select(self, name: str = 'default') -> 'Cache':
        """获取指定缓存数据库
        支持多次链式调用select方法
        永远不会改变app所绑定的默认缓存数据库
        :param name: 定义的数据库名，默认值为"default"
        :return: Cache对象
        """
        if name not in self._caches:
            raise AttributeError(f'Cache database "{name}" not found. '
                                 f'Please check CACHES config in settings')

        self._default = name
        return self


if __name__ == '__main__':
    from addict import Dict as asDict
    from config import config

    # 配置redis缓存组件, 前两项固定配置
    cacheConfig = asDict()
    cacheConfig.host = config.REDIS_HOST
    cacheConfig.port = config.REDIS_PORT
    cacheConfig.db = config.REDIS_DB
    cacheConfig.password = config.REDIS_PASSWORD
    cacheConfig.mode = "async"  # "async异步redis sync 同步redis"
    print(cacheConfig.to_dict())
    c = Cache(cacheConfig.to_dict())

    # redis缓存
    RedisClient = Cache().select('redis').current_db
    print(RedisClient)

    # 本地的内存缓存
    LocalMemoryEngine = Cache().select('default').current_db
    print(LocalMemoryEngine)
    LocalMemoryEngine.set("name", "mengying")
    print(LocalMemoryEngine.get("name"))
